﻿using NearAop.Attributes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace NearAop
{
    /// <summary>
    /// AOP参数信息
    /// </summary>
    public class AopParamInfo
    {
        private const BindingFlags ALL_MEMBRE = BindingFlags.Public
                    | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;

        private readonly Lazy<ParamNameMapping> paramNameMap;

        public AopParamInfo(Type type, string memberName)
        {
            MemberInfo = type.GetMembers(ALL_MEMBRE).First(x => memberName == x.ToString());
            paramNameMap = CreateParamNameMapping();
        }

        public AopParamInfo(Type type, int id)
        {
            MemberInfo = type.GetMembers(ALL_MEMBRE).First(x => ReflectionIdAttribute.IsTarget(x, id));
            paramNameMap = CreateParamNameMapping();
        }

        public MemberInfo MemberInfo { get; }

        internal ParamNameMapping Mapping => paramNameMap.Value;

        private Lazy<ParamNameMapping> CreateParamNameMapping()
        {
            return new Lazy<ParamNameMapping>(() =>
                   new ParamNameMapping(MemberInfo as MethodInfo));
        }

        public static Lazy<AopParamInfo> CreateFactory(Type type, string memberName)
        {
            return new Lazy<AopParamInfo>(() => new AopParamInfo(type, memberName));
        }

        public static Lazy<AopParamInfo> CreateFactory(MemberInfo member)
        {
            return new Lazy<AopParamInfo>(() => new AopParamInfo(member.DeclaringType, member.ToString()));
        }

        public static Lazy<AopParamInfo> CreateFactory<T>(int id)
        {
            return new Lazy<AopParamInfo>(() => new AopParamInfo(typeof(T), id));
        }
    }

    internal class ParamNameMapping
    {
        private readonly IDictionary<string, int> nameMap;

        public ParamNameMapping(MethodInfo methodInfo)
        {
            if (null == methodInfo) return;
            ParamNames = GetParamNames(methodInfo);
            nameMap = GetNameMap();
        }

        private string[] GetParamNames(MethodInfo methodInfo)
        {
            return methodInfo.GetParameters().Select(x => x.Name).ToArray();
        }

        private IDictionary<string, int> GetNameMap()
        {
            return ParamNames.Select((name, index) => new { name, index })
                .ToDictionary(x => x.name, x => x.index);
        }

        public string[] ParamNames { get; } = new string[] { };

        public bool TryGet(string name, out int index)
        {
            EmptyCheck();
            return nameMap.TryGetValue(name, out index);
        }

        public bool TryGet(int index, out string name)
        {
            EmptyCheck();
            name = index < ParamNames.Length ? ParamNames[index] : string.Empty;
            return name != string.Empty;
        }

        private void EmptyCheck()
        {
            if (nameMap != null) return;
            var err = $"MethodInfo = null";
            throw new Exception(err);
        }
    }
}